package com.benshou.bcss.common.j17common;

import com.benshou.bcss.common.j17common.annotation.NotNull;
import com.benshou.bcss.common.j17common.annotation.Nullable;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.NonNull;
import lombok.SneakyThrows;

import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

/**
 * @author <a href="mailto:wh1zper@qq.com">wh1zper</a>
 */
public class EnumSupport {

    private static final Cache<Class<?>, _EnumInfo<?>> ENUM_CACHE = CacheBuilder.newBuilder()
            .weakKeys()
            .softValues()
            .build();

    @SneakyThrows(ExecutionException.class)
    @Nullable
    public static <E extends Enum<E>, K> E findByKey(
            @NonNull Class<E> enumCls,
            @NonNull Function<E, @NotNull K> keyMapper,
            @NonNull K key) {
        @SuppressWarnings("unchecked")
        var enumInfo = (_EnumInfo<E>) ENUM_CACHE.get(enumCls, () -> {
            var enums = enumCls.getEnumConstants();
            var keyMapping = generateKeyMapping(enums, keyMapper);
            return new _EnumInfo<>(
                    enums,
                    keyMapper,
                    keyMapping,
                    CacheBuilder.newBuilder()
                            .weakKeys()
                            .softValues()
                            .build());});
        @SuppressWarnings("unchecked")
        var map = (Map<K, E>) (keyMapper == enumInfo.fastMapper() ?
                enumInfo.fastKeyMapping() :
                enumInfo.keyMappingCache().get(
                        keyMapper,
                        () -> generateKeyMapping(enumInfo.enums(), keyMapper)));
        return map.get(key);
    }

    @Nullable
    public static <E extends Enum<E> & Identifiable<K>, K> E findByKey(@NonNull Class<E> enumCls, @NonNull K key) {
        return findByKey(enumCls, Identifiable::identification, key);
    }

    @Nullable
    public static <E extends Enum<E>> E findByName(@NonNull Class<E> enumCls, @NonNull String name) {
        return findByKey(enumCls, Enum::name, name);
    }

    private static <E extends Enum<E>, K> Map<K, E> generateKeyMapping(
            @NotNull E[] enums,
            @NotNull Function<E, @NotNull K> keyMapper) {
        return Arrays.stream(enums)
                .collect(CollectorX.toUniqueKeyMap(
                        e -> Objects.requireNonNull(keyMapper.apply(e))));
    }

    private record _EnumInfo<E extends Enum<E>> (
            @NotNull E[] enums,
            @NotNull Function<E, @NotNull ?> fastMapper,
            @NotNull Map<?, E> fastKeyMapping,
            @NotNull Cache<Function<E, @NotNull ?>, Map<?, E>> keyMappingCache) {

    }
}
